2. Representación de mapas con el paquete tmap
Aunque hay muchos librerías para la representación de mapas (como
ggmap que ya hemos utilizado), hay librerías más
específicas que facilitarán muchas tareas. Una de ellas, que mantiene la
filosofía de ggplot, es tmap. Esta librería acepta
objetos tipo sp, mucho más efiecientes que los data
frames.
NOTA: Esta librería ha cambiado a principios de 2019
y los objetos almacenados son de tipo sf, esto afecta
al formato de los datos. Si procede se pueden cambiar a formato
sp con la función as(…,
‘Spatial’).
Referencia
sf (simple features) objects have a simpler structure than sp
objects. An sf object is nothing more than a data.frame with a special
geometry column that contains the geometries for the corresponding rows.
Such a geometry can be of type spatial point(s), line(s) or polygon(s)
or any combination of these in a ‘geometrycollection’ (see
vignette(“sf1”)). The layers functions, such as tm_polygons, will only
draw what they are supposed to draw (in this case polygons). The newly
added layer function tm_sf will draw all geometries
Primeros pasos con tmap:. Mapas
usando tmap
Información completa en el libro gratuito: Geocomputation with R
Usaremos un formato de representación por capas, similar a ggmap y a
ggplot:
- tm_shape() <–> ggplot() Es la forma más
general
- qtm() <–> qplot() Es la forma abreviada
Tipos de presentaciones para documentos html
tmap_mode(“plot”): para visualación estática
tmap_mode(“view”): para visualización interactiva.
Permite activar pop-up
La opción seleccionada se mantiene activa hasta que se indique lo
contrario.
Ejemplo de mapas en modo plot:
tmap_mode("plot")
data(World)
tm_shape(World) +
tm_polygons("HPI") # Happy Planet Index

Ejemplo de mapas en modo view:
tmap_mode("view")
data(World)
#World<-as(World,'Spatial') # No necesario, acepta objetos sp y sf
tm_shape(World) +
tm_polygons("HPI", id = "iso_a3", popup.vars = TRUE)
NA
Ejemplo. Gráfico básico. Quick Thematic Map (qtm)
Los datos utilizados están disponibles en la librería
tmap y también en http://www.naturalearthdata.com/
NOTA: En la última versón de tmaplos
ficheros vienen en formato sf en lugar de sp.
Después de la carga los trasformaremos a clase spcon la
función as por ejemplo World<-as(World,
‘Spatial’),si bien las funciones de representación del paquete
tmap acepta ambos formatos.
library(tmap)
data(World, rivers, metro)
str(World)
Classes ‘sf’ and 'data.frame': 177 obs. of 16 variables:
$ iso_a3 : Factor w/ 177 levels "AFG","AGO","ALB",..: 1 2 3 4 5 6 7 8 9 10 ...
$ name : Factor w/ 177 levels "Afghanistan",..: 1 4 2 166 6 7 5 56 8 9 ...
$ sovereignt : Factor w/ 171 levels "Afghanistan",..: 1 4 2 159 6 7 5 52 8 9 ...
$ continent : Factor w/ 8 levels "Africa","Antarctica",..: 3 1 4 3 8 3 2 7 6 4 ...
$ area : Units: [km^2] num 652860 1246700 27400 71252 2736690 ...
$ pop_est : num 28400000 12799293 3639453 4798491 40913584 ...
$ pop_est_dens: num 43.5 10.3 132.8 67.3 15 ...
$ economy : Factor w/ 7 levels "1. Developed region: G7",..: 7 7 6 6 5 6 6 6 2 2 ...
$ income_grp : Factor w/ 5 levels "1. High income: OECD",..: 5 3 4 2 3 4 2 2 1 1 ...
$ gdp_cap_est : num 784 8618 5993 38408 14027 ...
$ life_exp : num 59.7 NA 77.3 NA 75.9 ...
$ well_being : num 3.8 NA 5.5 NA 6.5 4.3 NA NA 7.2 7.4 ...
$ footprint : num 0.79 NA 2.21 NA 3.14 2.23 NA NA 9.31 6.06 ...
$ inequality : num 0.427 NA 0.165 NA 0.164 ...
$ HPI : num 20.2 NA 36.8 NA 35.2 ...
$ geometry :sfc_MULTIPOLYGON of length 177; first list element: List of 1
..$ :List of 1
.. ..$ : num [1:69, 1:2] 61.2 62.2 63 63.2 64 ...
..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
..- attr(*, "names")= chr [1:15] "iso_a3" "name" "sovereignt" "continent" ...
str(rivers)
Classes ‘sf’ and 'data.frame': 1616 obs. of 5 variables:
$ name : chr NA "Ebro" "Ebro" "Ebro" ...
$ type : Factor w/ 2 levels "Lake Centerline",..: 2 2 2 2 2 1 1 2 2 2 ...
$ scalerank: int 5 5 5 5 5 5 5 5 5 5 ...
$ strokelwd: num 2 1.5 2 2.5 3 2 2.5 1.5 2 2 ...
$ geometry :sfc_LINESTRING of length 1616; first list element: 'XY' num [1:5, 1:2] -73 -73.1 -73.1 -73.2 -73.2 ...
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA
..- attr(*, "names")= chr [1:4] "name" "type" "scalerank" "strokelwd"
str(metro)
Classes ‘sf’ and 'data.frame': 436 obs. of 13 variables:
$ name : chr "Kabul" "Algiers" "Luanda" "Buenos Aires" ...
$ name_long: chr "Kabul" "El Djazair (Algiers)" "Luanda" "Buenos Aires" ...
$ iso_a3 : chr "AFG" "DZA" "AGO" "ARG" ...
$ pop1950 : num 170784 516450 138413 5097612 429249 ...
$ pop1960 : num 285352 871636 219427 6597634 605309 ...
$ pop1970 : num 471891 1281127 459225 8104621 809794 ...
$ pop1980 : num 977824 1621442 771349 9422362 1009521 ...
$ pop1990 : num 1549320 1797068 1390240 10513284 1200168 ...
$ pop2000 : num 2401109 2140577 2591388 12406780 1347561 ...
$ pop2010 : num 3722320 2432023 4508434 14245871 1459268 ...
$ pop2020 : num 5721697 2835218 6836849 15894307 1562509 ...
$ pop2030 : num 8279607 3404575 10428756 16956491 1718192 ...
$ geometry :sfc_POINT of length 436; first list element: 'XY' num 69.2 34.5
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
..- attr(*, "names")= chr [1:12] "name" "name_long" "iso_a3" "pop1950" ...
Observar la diferencia entre las estructuras de sf y
sp.
Ejercicio: Dibuja el mapa de España con qtm(), en modo
interactivo.
Choropleth Map.
Observa el código y cómo las opciones incluidas modifican el aspecto
del mapa.
# choropleth
qtm(World, fill = "economy", format="World", style="col_blind")
tmap_mode('view')
qtm(World, fill="HPI", fill.n=9, fill.palette="div", fill.auto.palette.mapping=FALSE,
fill.title="Happy Planet Index", fill.id="name", format="World", style="gray")
qtm(World, fill="area", fill.n=9, fill.palette="div", fill.auto.palette.mapping=TRUE,
fill.title="Area", fill.id="area", format="World", style="gray")
NA
Bubble map
Superponemos dos objetos SP en un mapa. Filosofía análoga a
ggplot.
qtm(World, borders = NULL) +
qtm(metro, symbols.size = "pop2010",
symbols.title.size= "Metropolitan Areas",
symbols.id= "name",
format = "World")
Otro ejemplo de Choropleth
Aquí representamos dos magnitudes: “well being” en el color de los
polígonos, y “area”, proporcional al tamaño del texto.
data(World)
Europe<-World[World$continent=='Europe',]
tmap_mode('plot') # en modo view no funciona el escalado de las etiquetas
tm_shape(Europe) +
tm_polygons("well_being", textNA="Non-European countries", title="Well-Being Index") +
tm_text("iso_a3", size="AREA", root=5) +
tm_format("World") +
tm_style("grey")

NOTA: No está disponible el mapa ‘Europe’ como tal
en la última versión de tmap (no podemos hacer data(Europe), sino que
debemos restringir ‘World’ por continente.
Ejemplo de Superposición de múltiples objetos sp en
un mismo mapa.
Comandos para la representación de mapas con tmap:
Observa el procedimiento para superponer múltiples objetos
(SpatialPoints, SpatialLines y
SpatialPolygons) en un mismo mapa.
En primer lugar observamos la estructura de cada uno de los
objetos
# Cargamos los datos (con conjuntos incluidos en las librerías)
#data(package='tmap')
data(land, rivers, metro) # land cover, rivers, metropolitan areas
# land es de tipo raster (stars-raster), las otras dos son de tipo sf
# Transformamos SF en SP
rivers<-as(rivers,'Spatial')
metro<-as(metro, 'Spatial')
# Salvo para usar estos códigos no es necesario pasar a tipo sp
str(land,max.level = 2)
List of 4
$ cover : Factor[1:1080, 1:540] w/ 20 levels "Broadleaf Evergreen Forest",..: 20 20 20 20 20 20 20 20 20 20 ...
$ cover_cls: Factor[1:1080, 1:540] w/ 8 levels "Forest","Other natural vegetation",..: 8 8 8 8 8 8 8 8 8 8 ...
$ trees : int [1:1080, 1:540] NA NA NA NA NA NA NA NA NA NA ...
$ elevation: int [1:1080, 1:540] NA NA NA NA NA NA NA NA NA NA ...
- attr(*, "dimensions")=List of 2
..$ x:List of 7
.. ..- attr(*, "class")= chr "dimension"
..$ y:List of 7
.. ..- attr(*, "class")= chr "dimension"
..- attr(*, "raster")=List of 3
.. ..- attr(*, "class")= chr "stars_raster"
..- attr(*, "class")= chr "dimensions"
- attr(*, "class")= chr "stars"
##str(land@data)
str(rivers,max.level = 2)
Formal class 'SpatialLinesDataFrame' [package "sp"] with 4 slots
..@ data :'data.frame': 1616 obs. of 4 variables:
..@ lines :List of 1616
..@ bbox : num [1:2, 1:2] -165.2 -50.2 176.3 73.3
.. ..- attr(*, "dimnames")=List of 2
..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
str(rivers@data)
'data.frame': 1616 obs. of 4 variables:
$ name : chr NA "Ebro" "Ebro" "Ebro" ...
$ type : Factor w/ 2 levels "Lake Centerline",..: 2 2 2 2 2 1 1 2 2 2 ...
$ scalerank: int 5 5 5 5 5 5 5 5 5 5 ...
$ strokelwd: num 2 1.5 2 2.5 3 2 2.5 1.5 2 2 ...
str(metro,max.level = 2)
Formal class 'SpatialPointsDataFrame' [package "sp"] with 5 slots
..@ data :'data.frame': 436 obs. of 12 variables:
..@ coords.nrs : num(0)
..@ coords : num [1:436, 1:2] 69.17 3.04 13.23 -58.4 -64.18 ...
.. ..- attr(*, "dimnames")=List of 2
..@ bbox : num [1:2, 1:2] -123.1 -37.8 174.8 60.2
.. ..- attr(*, "dimnames")=List of 2
..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
str(metro@data)
'data.frame': 436 obs. of 12 variables:
$ name : chr "Kabul" "Algiers" "Luanda" "Buenos Aires" ...
$ name_long: chr "Kabul" "El Djazair (Algiers)" "Luanda" "Buenos Aires" ...
$ iso_a3 : chr "AFG" "DZA" "AGO" "ARG" ...
$ pop1950 : num 170784 516450 138413 5097612 429249 ...
$ pop1960 : num 285352 871636 219427 6597634 605309 ...
$ pop1970 : num 471891 1281127 459225 8104621 809794 ...
$ pop1980 : num 977824 1621442 771349 9422362 1009521 ...
$ pop1990 : num 1549320 1797068 1390240 10513284 1200168 ...
$ pop2000 : num 2401109 2140577 2591388 12406780 1347561 ...
$ pop2010 : num 3722320 2432023 4508434 14245871 1459268 ...
$ pop2020 : num 5721697 2835218 6836849 15894307 1562509 ...
$ pop2030 : num 8279607 3404575 10428756 16956491 1718192 ...
Representamos todos simultáneamente (luego explicaremos paso a
paso)
tmap_mode('plot')
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers) +
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE) +
tm_shape(metro) +
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1,
size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6),
title.size="Metropolitan Population") +
tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6,
bg.color="white", bg.alpha = .75,
auto.placement = 1, legend.size.show = FALSE) +
tm_format("World") +
tm_style("natural")

Paso a paso
# capa de land cover con una capa raster de porcentaje de superficie cubierta por árboles (gradiente de color dado por breaks)
p<-tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE)
show(p)

Paso 1
# Añadir capa de datos de Europa y definirlo como capa maestra, mostrar sus fronteras
p<-tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders()
show(p)

Paso 2
# Añadir capa de ríos, escalados por su caudal
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)

Paso 3
# Añadir capa de burbujas, de tamaño proporcional a la población metropolitana
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1,
size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6),
title.size="Metropolitan Population")

NA
Paso 4
# Añadir capa de texto de nombres de metrópolis
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1,
size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6),
title.size="Metropolitan Population")+
tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6,
bg.color="white", bg.alpha = .75,
auto.placement = 1, legend.size.show = FALSE)

NA
NA
Paso 5
# Añadir formato: available formats are: "World", "World_wide", "NLD", "NLD_wide"
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1,
size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6),
title.size="Metropolitan Population")+
tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6,
bg.color="white", bg.alpha = .75,
auto.placement = 1, legend.size.show = FALSE)+
tm_format("World")

Paso 6
# estilo de representación (gama de colores)
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1,
size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6),
title.size="Metropolitan Population")+
tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6,
bg.color="white", bg.alpha = .75,
auto.placement = 1, legend.size.show = FALSE)+
tm_format("World") +
tm_style("natural")

NA
Consideraciones sobre el mapa anterior
- Este mapa tiene 4 grupos de capas, respectivamente los objetos
land, Europe, rivers, y metro. El orden de grupo (capa)
corresponde al orden en que se dibuja.
- Los objetos pueden tener diferentes proyecciones y
también pueden cubrir diferentes áreas (bounding
boxes). Tanto la
proyección como
el área cubierta se toman por defecto del objeto definido
en la primera tm_shape, pero en este caso en la segunda
tm_shape ya que es en la que se ha usado
is.master=TRUE. Ten en cuenta que todos los objetos
tienen elementos fuera de Europa (ver por ejemplo
qtm(rivers)).
Se puede además añadir una capa tm_layout() que
controla aspectos como título, márgenes, relación de aspecto, etc.
tmap_mode('plot')
tm_shape(rivers)+tm_lines()+tm_layout(main.title ="Ríos",main.title.position = "center")
# Grosor de la línea dependiente de una variable.
tm_shape(rivers)+tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+tm_layout(main.title ="Ríos",main.title.position = "center")
tm_shape(metro)+tm_dots()+tm_layout(main.title ="Metro",main.title.position = "center")
Ejemplos:
Representación de dos mapas simultánemente con asignación de CRS
# Proyección Robinson
tmap_mode('plot')
robin <- "+proj=robin +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
m1 <- tm_shape(World, projection = robin) +
tm_polygons(c("HPI", "gdp_cap_est"),
palette = list("RdYlGn", "Purples"),
style = c("pretty", "fixed"), n = 7,
breaks = list(NULL, c(0, 500, 2000, 5000, 10000, 25000, 50000, Inf)),
title = c("Happy Planet Index", "GDP per capita")) +
tm_style("natural", earth.boundary = c(-180, -87, 180, 87)) +
tm_format("World", inner.margins = 0.02, frame = FALSE) +
tm_legend(position = c("left", "bottom"), bg.color = "gray95", frame = TRUE) +
tm_credits(c("", "Robinson projection"), position = c("RIGHT", "BOTTOM"))
m1
Visualización de tres magnitudes en el mismo mapa
tmap_mode('plot')
metro$growth <- (metro$pop2020 - metro$pop2010) / (metro$pop2010 * 10) * 100
m2 <- tm_shape(World) +
tm_polygons("income_grp", palette = "-Blues",
title = "Income class", contrast = 0.7, border.col = "grey30", id = "name") +
tm_text("iso_a3", size = "AREA", col = "grey30", root = 3) +
tm_shape(metro) +
tm_bubbles("pop2010", col = "growth", border.col = "black",
border.alpha = 0.5,
breaks = c(-Inf, 0, 2, 4, 6, Inf) ,
palette = "-RdYlGn",
title.size = "Metro population (2010)",
title.col = "Annual growth rate (%)",
id = "name",
popup.vars = c("pop2010", "pop2020", "growth")) +
tm_style("gray") +
tm_format("World", frame.lwd = 2)
m2
Fijar un “marker” mediante coordenadas geográficas a partir de
dirección.
# obtain geocode address information
etse <- geocode_OSM('ETSE, Burjassot, Spain', as.sf = TRUE)
# change to interactive mode
tmap_mode("view")
tm_shape(etse) +
tm_markers(text="query")
Guardar un mapa: tmap_save()
Permite grabar mapas estáticos y tambien INTERACTIVOS.
library(sp)
library(tmap)
library(geospatial)
tmap_mode('plot')
tm_shape(countries_spdf) +
tm_grid(n.x = 11, n.y = 11, projection = "+proj=longlat") +
tm_fill(col = "population", style = "quantile",alpha = 0.2) +
tm_borders(col = "burlywood4")
# Guardar un mapa ESTÁTICO
tmap_save(filename="population.png")
# Save un mapa INTERACTIVO
tmap_mode('view')
tm_shape(countries_spdf) +
tm_grid(n.x = 11, n.y = 11, projection = "+proj=longlat") +
tm_fill(col = "population", style = "quantile",alpha = 0.2) +
tm_borders(col = "burlywood4")
tmap_save(filename="population.html")
# La opción por defecto, cuando se utiliza tmap es que el mapa sea interactivo.
Integración en shiny
Podemos incrustar mapas de tmap en shiny con la función
tmapOutput() en la parte de UI y
renderTmap() en el server:
library(shiny)
ui <- fluidPage(
tmapOutput("my_tmap")
)
server <- function(input,output) {
output$my_tmap = renderTmap({
tm_shape(World, projection="+proj=robin") + tm_polygons("HPI", legend.title = "Happy Planet Index") + tm_style('cobalt')
})
}
shinyApp(ui, server)
LS0tDQp0aXRsZTogJ01hc3RlciBlbiBDaWVuY2lhIGRlIERhdG9zOiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyBlc3BhY2lhbGVzJw0Kc3VidGl0bGU6ICdQYXJ0ZSAyOiBWaXN1YWxpemFjaW9uZXMgZGUgbWFwYXMgY29uICoqdG1hcCoqJw0KYXV0aG9yOiAiRmVybmFuZG8gTWF0ZW8iDQpkYXRlOiAgImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnNCcNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnNCcNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyMgR2xvYmFsIGNvZGUgb3B0aW9ucw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwNCgkgICAgICAgICAgICAgY2FjaGU9VFJVRSwNCiAgICAgICAgICAgICAgIHByb21wdD1GQUxTRSwNCiAgICAgICAgICAgICAgIHRpZHk9VFJVRSwNCiAgICAgICAgICAgICAgIGNvbW1lbnQ9TkEsDQogICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLA0KICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSkNCg0KYGBgDQoNCiMjIDEuIEluc3RhbGFjacOzbiBkZSBsaWJyZXLDrWFzLg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCiMgRXNwZWNpZmljYW1vcyBsYXMgbGlicmVyw61hcyBuZWNlc2FyaWFzIGVuIGVzdGEgbGlzdGENCg0KcGFja2FnZXMgPSBjKCJtYXBzIiwiZ2dtYXAiLCJsZWFmbGV0IiwicmdkYWwiLCJyZ2VvcyIsIm1hcHRvb2xzIiwidGlkeXZlcnNlIiwidG1hcCIsImRldnRvb2xzIiwiZm9ybWF0UiIsInRtYXB0b29scyIsImNhcmV0IiwnbWFwdmlldycsJ2NhcnRvZ3JhcGh5JykNCg0KDQojdXNlIHRoaXMgZnVuY3Rpb24gdG8gY2hlY2sgaWYgZWFjaCBwYWNrYWdlIGlzIG9uIHRoZSBsb2NhbCBtYWNoaW5lDQojaWYgYSBwYWNrYWdlIGlzIGluc3RhbGxlZCwgaXQgd2lsbCBiZSBsb2FkZWQNCiNpZiBhbnkgYXJlIG5vdCwgdGhlIG1pc3NpbmcgcGFja2FnZShzKSB3aWxsIGJlIGluc3RhbGxlZCBhbmQgbG9hZGVkDQpwYWNrYWdlLmNoZWNrIDwtIGxhcHBseShwYWNrYWdlcywgRlVOID0gZnVuY3Rpb24oeCkgew0KICBpZiAoIXJlcXVpcmUoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoeCwgZGVwZW5kZW5jaWVzID0gVFJVRSxyZXBvcz0naHR0cDovL2NyYW4ucmVkaXJpcy5lcycpDQogICAgbGlicmFyeSh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQogIH0NCn0pDQoNCiN2ZXJpZnkgdGhleSBhcmUgbG9hZGVkDQpzZWFyY2goKQ0KDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdnbWFwKQ0KbGlicmFyeShyZ2RhbCkNCmxpYnJhcnkocmdlb3MpDQpsaWJyYXJ5KG1hcHRvb2xzKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KHRtYXApDQpsaWJyYXJ5KGdlb3NwYXRpYWwpICMgSW5zdGFsYWRvIGVuIGVqZXJjaWNpb3MgYW50ZXJpb3JlcyANCmxpYnJhcnkoc3ApDQpsaWJyYXJ5KG1hcHMpDQpsaWJyYXJ5KHJhc3RlcikNCmxpYnJhcnkodG1hcHRvb2xzKQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShjYXJ0b2dyYXBoeSkNCmBgYA0KDQojIyAyLiBSZXByZXNlbnRhY2nDs24gZGUgbWFwYXMgY29uIGVsIHBhcXVldGUgKip0bWFwKioNCg0KQXVucXVlIGhheSBtdWNob3MgbGlicmVyw61hcyBwYXJhIGxhIHJlcHJlc2VudGFjacOzbiBkZSBtYXBhcyAoY29tbyBgZ2dtYXBgIHF1ZSB5YSBoZW1vcyB1dGlsaXphZG8pLCBoYXkgbGlicmVyw61hcyBtw6FzIGVzcGVjw61maWNhcyBxdWUgZmFjaWxpdGFyw6FuIG11Y2hhcyB0YXJlYXMuIFVuYSBkZSBlbGxhcywgcXVlIG1hbnRpZW5lIGxhIGZpbG9zb2bDrWEgZGUgZ2dwbG90LCBlcyAqKnRtYXAqKi4gRXN0YSBsaWJyZXLDrWEgYWNlcHRhIG9iamV0b3MgdGlwbyAqKnNwKiosIG11Y2hvIG3DoXMgZWZpZWNpZW50ZXMgcXVlIGxvcyBkYXRhIGZyYW1lcy4NCg0KKipOT1RBOioqIEVzdGEgbGlicmVyw61hIGhhIGNhbWJpYWRvIGEgcHJpbmNpcGlvcyBkZSAyMDE5IHkgbG9zIG9iamV0b3MgYWxtYWNlbmFkb3Mgc29uIGRlIHRpcG8gKipzZioqLCBlc3RvIGFmZWN0YSBhbCBmb3JtYXRvIGRlIGxvcyBkYXRvcy4gU2kgcHJvY2VkZSBzZSBwdWVkZW4gY2FtYmlhciBhIGZvcm1hdG8gKipzcCoqIGNvbiBsYSBmdW5jacOzbiAqKmFzKC4uLiwgJ1NwYXRpYWwnKSoqLg0KDQpbUmVmZXJlbmNpYV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtY2hhbmdlcy12Mi5odG1sKQ0KX3NmIChzaW1wbGUgZmVhdHVyZXMpIG9iamVjdHMgaGF2ZSBhIHNpbXBsZXIgc3RydWN0dXJlIHRoYW4gc3Agb2JqZWN0cy4gQW4gc2Ygb2JqZWN0IGlzIG5vdGhpbmcgbW9yZSB0aGFuIGEgZGF0YS5mcmFtZSB3aXRoIGEgc3BlY2lhbCBnZW9tZXRyeSBjb2x1bW4gdGhhdCBjb250YWlucyB0aGUgZ2VvbWV0cmllcyBmb3IgdGhlIGNvcnJlc3BvbmRpbmcgcm93cy4gU3VjaCBhIGdlb21ldHJ5IGNhbiBiZSBvZiB0eXBlIHNwYXRpYWwgcG9pbnQocyksIGxpbmUocykgb3IgcG9seWdvbihzKSBvciBhbnkgY29tYmluYXRpb24gb2YgdGhlc2UgaW4gYSDigJhnZW9tZXRyeWNvbGxlY3Rpb27igJkgKHNlZSB2aWduZXR0ZSgic2YxIikpLiBUaGUgbGF5ZXJzIGZ1bmN0aW9ucywgc3VjaCBhcyB0bV9wb2x5Z29ucywgd2lsbCBvbmx5IGRyYXcgd2hhdCB0aGV5IGFyZSBzdXBwb3NlZCB0byBkcmF3IChpbiB0aGlzIGNhc2UgcG9seWdvbnMpLiBUaGUgbmV3bHkgYWRkZWQgbGF5ZXIgZnVuY3Rpb24gdG1fc2Ygd2lsbCBkcmF3IGFsbCBnZW9tZXRyaWVzXw0KDQoNCioqUHJpbWVyb3MgcGFzb3MgY29uIHRtYXA6KiouIFtNYXBhcyB1c2FuZG8gdG1hcF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtZ2V0c3RhcnRlZC5odG1sKQ0KDQoqKkluZm9ybWFjacOzbiBjb21wbGV0YSBlbiBlbCBsaWJybyBncmF0dWl0bzoqKiBbR2VvY29tcHV0YXRpb24gd2l0aCBSXShodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0LykNCg0KDQpVc2FyZW1vcyB1biBmb3JtYXRvIGRlIHJlcHJlc2VudGFjacOzbiBwb3IgY2FwYXMsIHNpbWlsYXIgYSBnZ21hcCB5IGEgZ2dwbG90Og0KDQorICoqdG1fc2hhcGUoKSA8LS0+IGdncGxvdCgpKiogRXMgbGEgZm9ybWEgbcOhcyBnZW5lcmFsDQorICoqcXRtKCkgPC0tPiBxcGxvdCgpKiogRXMgbGEgZm9ybWEgYWJyZXZpYWRhDQoNCiMjIyBUaXBvcyBkZSBwcmVzZW50YWNpb25lcyBwYXJhIGRvY3VtZW50b3MgaHRtbA0KDQoqKnRtYXBfbW9kZSgicGxvdCIpKio6IHBhcmEgdmlzdWFsYWNpw7NuIGVzdMOhdGljYQ0KKip0bWFwX21vZGUoInZpZXciKSoqOiBwYXJhIHZpc3VhbGl6YWNpw7NuIGludGVyYWN0aXZhLiBQZXJtaXRlIGFjdGl2YXIgYHBvcC11cGANCg0KTGEgb3BjacOzbiBzZWxlY2Npb25hZGEgc2UgbWFudGllbmUgYWN0aXZhIGhhc3RhIHF1ZSBzZSBpbmRpcXVlIGxvIGNvbnRyYXJpby4NCg0KRWplbXBsbyBkZSBtYXBhcyBlbiBtb2RvIHBsb3Q6DQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCJwbG90IikNCmRhdGEoV29ybGQpDQp0bV9zaGFwZShXb3JsZCkgKw0KICB0bV9wb2x5Z29ucygiSFBJIikgIyBIYXBweSBQbGFuZXQgSW5kZXgNCmBgYA0KDQpFamVtcGxvIGRlIG1hcGFzIGVuIG1vZG8gdmlldzoNCg0KYGBge3J9DQp0bWFwX21vZGUoInZpZXciKQ0KZGF0YShXb3JsZCkNCiNXb3JsZDwtYXMoV29ybGQsJ1NwYXRpYWwnKSAjIE5vIG5lY2VzYXJpbywgYWNlcHRhIG9iamV0b3Mgc3AgeSBzZg0KdG1fc2hhcGUoV29ybGQpICsNCiAgdG1fcG9seWdvbnMoIkhQSSIsIGlkID0gImlzb19hMyIsIHBvcHVwLnZhcnMgPSBUUlVFKQ0KDQpgYGANCg0KIyMjIEVqZW1wbG8uIEdyw6FmaWNvIGLDoXNpY28uIFF1aWNrIFRoZW1hdGljIE1hcCAocXRtKQ0KDQpMb3MgZGF0b3MgdXRpbGl6YWRvcyBlc3TDoW4gZGlzcG9uaWJsZXMgZW4gbGEgbGlicmVyw61hICoqdG1hcCoqIHkgdGFtYmnDqW4gZW4gW2h0dHA6Ly93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vXShodHRwOi8vd3d3Lm5hdHVyYWxlYXJ0aGRhdGEuY29tLykgDQoNCioqTk9UQSoqOiBFbiBsYSDDumx0aW1hIHZlcnPDs24gZGUgYHRtYXBgbG9zIGZpY2hlcm9zIHZpZW5lbiBlbiBmb3JtYXRvIGBzZmAgZW4gbHVnYXIgZGUgYHNwYC4gRGVzcHXDqXMgZGUgbGEgY2FyZ2EgbG9zIHRyYXNmb3JtYXJlbW9zIGEgY2xhc2UgYHNwYGNvbiBsYSBmdW5jacOzbiBgYXNgIHBvciBlamVtcGxvICoqV29ybGQ8LWFzKFdvcmxkLCAnU3BhdGlhbCcpKiosc2kgYmllbiBsYXMgZnVuY2lvbmVzIGRlIHJlcHJlc2VudGFjacOzbiBkZWwgcGFxdWV0ZSAqdG1hcCogYWNlcHRhIGFtYm9zIGZvcm1hdG9zLg0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHRtYXApDQoNCmRhdGEoV29ybGQsIHJpdmVycywgbWV0cm8pDQoNCnN0cihXb3JsZCkNCnN0cihyaXZlcnMpDQpzdHIobWV0cm8pDQpgYGANCg0KT2JzZXJ2YXIgbGEgZGlmZXJlbmNpYSBlbnRyZSBsYXMgZXN0cnVjdHVyYXMgZGUgKipzZioqIHkgKipzcCoqLg0KDQpgYGB7cn0NCldvcmxkPC1hcyhXb3JsZCwgJ1NwYXRpYWwnKSAgIyBjb252aWVydGUgZGUgc2YgYSBzcA0Kcml2ZXJzPC1hcyhyaXZlcnMsICdTcGF0aWFsJykNCm1ldHJvPC1hcyhtZXRybywgJ1NwYXRpYWwnKQ0KDQoNCnN0cihXb3JsZCxtYXgubGV2ZWwgPSAyKQ0Kc3RyKFdvcmxkQGRhdGEpICMgUGFyYSBmb3JtYXRvIHNwLCBzaSBtYW50ZW5lbW9zIGVsIGZvcm1hdG8gc2YsIHNlIGFjY2VkZSBkaXJlY3RhbWVudGUgYSBsYSB2YXJpYWJsZSBjb21vIGVuIHVuIGRhdGFmcmFtZSBub3JtYWwNCiNnZHBfbWQ6IHByb2R1Y3RvIGludGVyaW9yIGJydXRvDQojZ2RwX2NhcDogcGVyIGNhcGl0YQ0KI0hQSTogSGFwcHkgUGxhbmV0IEluZGV4IChHcmFkbyBkZSBmZWxpY2lkYWQpDQoNCiMgc29sbyBlbCBtYXBhDQpxdG0oc2hwPVdvcmxkKSAjIHNocDogbWFwYS4gQ2FtYmlhciBwb3IgIG1ldHJvLCByaXZlcnMuLi4NCmBgYA0KDQoNCkVqZXJjaWNpbzogRGlidWphIGVsIG1hcGEgZGUgRXNwYcOxYSBjb24gcXRtKCksIGVuIG1vZG8gaW50ZXJhY3Rpdm8uDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCd2aWV3JykNCnF0bShXb3JsZFtXb3JsZCRuYW1lPT0nU3BhaW4nLF0pDQpgYGANCg0KIyMjIENob3JvcGxldGggIE1hcC4NCg0KT2JzZXJ2YSBlbCBjw7NkaWdvIHkgY8OzbW8gbGFzIG9wY2lvbmVzIGluY2x1aWRhcyBtb2RpZmljYW4gZWwgYXNwZWN0byBkZWwgbWFwYS4NCg0KYGBge3J9DQojIGNob3JvcGxldGgNCnF0bShXb3JsZCwgZmlsbCA9ICJlY29ub215IiwgZm9ybWF0PSJXb3JsZCIsIHN0eWxlPSJjb2xfYmxpbmQiKQ0KYGBgDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCd2aWV3JykNCnF0bShXb3JsZCwgZmlsbD0iSFBJIiwgZmlsbC5uPTksIGZpbGwucGFsZXR0ZT0iZGl2IiwgZmlsbC5hdXRvLnBhbGV0dGUubWFwcGluZz1GQUxTRSwgDQoJZmlsbC50aXRsZT0iSGFwcHkgUGxhbmV0IEluZGV4IiwgZmlsbC5pZD0ibmFtZSIsIGZvcm1hdD0iV29ybGQiLCBzdHlsZT0iZ3JheSIpDQoNCnF0bShXb3JsZCwgZmlsbD0iYXJlYSIsIGZpbGwubj05LCBmaWxsLnBhbGV0dGU9ImRpdiIsIGZpbGwuYXV0by5wYWxldHRlLm1hcHBpbmc9VFJVRSwgDQoJZmlsbC50aXRsZT0iQXJlYSIsIGZpbGwuaWQ9ImFyZWEiLCBmb3JtYXQ9IldvcmxkIiwgc3R5bGU9ImdyYXkiKQ0KDQpgYGANCg0KIyMjIEJ1YmJsZSBtYXANClN1cGVycG9uZW1vcyBkb3Mgb2JqZXRvcyBTUCBlbiB1biBtYXBhLiBGaWxvc29mw61hIGFuw6Fsb2dhIGEgYGdncGxvdGAuDQoNCmBgYHtyfQ0KcXRtKFdvcmxkLCBib3JkZXJzID0gTlVMTCkgKyANCnF0bShtZXRybywgc3ltYm9scy5zaXplID0gInBvcDIwMTAiLCANCiAgICBzeW1ib2xzLnRpdGxlLnNpemU9ICJNZXRyb3BvbGl0YW4gQXJlYXMiLCANCiAgICBzeW1ib2xzLmlkPSAibmFtZSIsDQogICAgZm9ybWF0ID0gIldvcmxkIikNCmBgYA0KDQojIyMgRWplbXBsbyBVc2FuZG8gbGEgZm9ybWEgZ2VuZXJhbCAqKnRtX1hYWCoqLg0KDQpVc2Ftb3MgZWwgb2JqZXRvICoqY291bnRyaWVzX3NwZGYqKi4gRW4gcHJpbWVyIGx1Z2FyIGF2ZXJpZ3VhbW9zIGxhcyB2YXJpYWJsZXMgYWxtYWNlbmFkYXMgZW4gZWwgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQpsaWJyYXJ5KHNwKQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShnZW9zcGF0aWFsKQ0KDQpzdHIoY291bnRyaWVzX3NwZGYsbWF4LmxldmVsID0gMikNCnN0cihjb3VudHJpZXNfc3BkZkBkYXRhKQ0KDQpgYGANCg0KTGEgY2FwYSAqKnRtX3NoYXBlKCkqKiBlcyBjb21vIGxhIGZ1bmNpw7NuIGdncGxvdCgpLiBDb250aWVuZSBlbCBvYmpldG8gKipzcCoqLyoqc2YqKiBjb24gbG9zIGRhdG9zIHkgZXMgbGEgYmFzZSBzb2JyZSBsYSBxdWUgc2Ugc3VwZXJwb25lbiBsYXMgZGVtw6FzIGNhcGFzLiBQb3N0ZXJpb3JtZW50ZSB2ZXJlbW9zIHF1ZSBwb2RlbW9zIHN1cGVycG9uZXIgdmFyaWFzIGNhcGFzYSAqKnRtX3NoYXBlKCkqKiBkZSBkaWZlcmVudGVzIG9iamV0b3MgKipzcCoqIGVuIHVuIG1pc21vIG1hcGEuDQoNCkxhIGNhcGEgKip0bV9maWxsKCkqKiBoYWNlIGVsIHJlbGxlbm8gZGUgbG9zIHBvbMOtZ29ub3MgZGUgbWFuZXJhIHNpbWlsYXIgYSAqKnRtX3BvbHlnb24oY29sID0gIi4uLiIpKiouIFZlYW1vcyB1biBlamVtcGxvLg0KDQpgYGB7cn0NCiAgcDwtdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpIA0KICBwPC1wK3RtX2ZpbGwoY29sID0gInBvcHVsYXRpb24iLCBzdHlsZT0icXVhbnRpbGUiKSAjIGNvbnN1bHRhIGVuIHF1w6kgY29uc2lzdGUgZWwgZXN0aWxvICJxdWFudGlsZSINCiAgc2hvdyhwKQ0KYGBgDQpTaW50YXhpcyBhbHRlcm5hdGl2YQ0KDQpgYGB7cn0NCiAgdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpICsgdG1fZmlsbChjb2wgPSAicG9wdWxhdGlvbiIsc3R5bGU9InF1YW50aWxlIikgDQpgYGANCg0KQcOxYWRlIHVuYSBjYXBhICoqdG1fYm9yZGVycygpKiogY29uIGVsIGF0cmlidXRvIGNvbCA9ICJidXJseXdvb2QiLiBPYnNlcnZhIGVsIGVmZWN0by4NCg0KYGBge3J9DQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikrdG1fZmlsbChjb2w9J3BvcHVsYXRpb24nKQ0KDQpgYGANCg0KQcOxYWRlIHVuYSBjYXBhICoqdG1fYnViYmxlcygpKiogY29uIHNpemUgPSAicG9wdWxhdGlvbiIgeSBjb2w9ImdyZWVuIi4gQ2FtYmlhIGVsIGNvbG9yIGRlIGxhcyBmcm9udGVyYXMgYSAiYmxhY2siDQoNCg0KYGBge3J9DQoNCg0KYGBgDQoNCg0KIyMjIE90cm8gZWplbXBsbyBkZSBDaG9yb3BsZXRoDQoNCkFxdcOtIHJlcHJlc2VudGFtb3MgZG9zIG1hZ25pdHVkZXM6ICJ3ZWxsIGJlaW5nIiBlbiBlbCBjb2xvciBkZSBsb3MgcG9sw61nb25vcywgeSAiYXJlYSIsIHByb3BvcmNpb25hbCBhbCB0YW1hw7FvIGRlbCB0ZXh0by4NCg0KYGBge3J9DQpkYXRhKFdvcmxkKQ0KRXVyb3BlPC1Xb3JsZFtXb3JsZCRjb250aW5lbnQ9PSdFdXJvcGUnLF0NCg0KdG1hcF9tb2RlKCdwbG90JykgIyBlbiBtb2RvIHZpZXcgbm8gZnVuY2lvbmEgZWwgZXNjYWxhZG8gZGUgbGFzIGV0aXF1ZXRhcw0KdG1fc2hhcGUoRXVyb3BlKSArDQogICAgdG1fcG9seWdvbnMoIndlbGxfYmVpbmciLCB0ZXh0TkE9Ik5vbi1FdXJvcGVhbiBjb3VudHJpZXMiLCB0aXRsZT0iV2VsbC1CZWluZyBJbmRleCIpICsNCiAgICB0bV90ZXh0KCJpc29fYTMiLCBzaXplPSJBUkVBIiwgcm9vdD01KSArIA0KdG1fZm9ybWF0KCJXb3JsZCIpICsNCnRtX3N0eWxlKCJncmV5IikNCmBgYA0KDQoqKk5PVEE6KiogTm8gZXN0w6EgZGlzcG9uaWJsZSBlbCBtYXBhICdFdXJvcGUnIGNvbW8gdGFsIGVuIGxhIMO6bHRpbWEgdmVyc2nDs24gZGUgdG1hcCAobm8gcG9kZW1vcyBoYWNlciBkYXRhKEV1cm9wZSksIHNpbm8gcXVlIGRlYmVtb3MgcmVzdHJpbmdpciAnV29ybGQnIHBvciBjb250aW5lbnRlLg0KDQoNCiMjIyBFamVtcGxvIGRlIFN1cGVycG9zaWNpw7NuIGRlIG3Dumx0aXBsZXMgb2JqZXRvcyAqKnNwKiogZW4gdW4gbWlzbW8gbWFwYS4NCg0KQ29tYW5kb3MgcGFyYSBsYSByZXByZXNlbnRhY2nDs24gZGUgbWFwYXMgY29uIFt0bWFwXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdG1hcC92aWduZXR0ZXMvdG1hcC1nZXRzdGFydGVkLmh0bWwpOiANCg0KT2JzZXJ2YSBlbCBwcm9jZWRpbWllbnRvIHBhcmEgc3VwZXJwb25lciBtw7psdGlwbGVzIG9iamV0b3MgKCoqU3BhdGlhbFBvaW50cyoqLCAqKlNwYXRpYWxMaW5lcyoqIHkgKipTcGF0aWFsUG9seWdvbnMqKikgZW4gdW4gbWlzbW8gbWFwYS4NCg0KRW4gcHJpbWVyIGx1Z2FyIG9ic2VydmFtb3MgbGEgZXN0cnVjdHVyYSBkZSBjYWRhIHVubyBkZSBsb3Mgb2JqZXRvcw0KDQpgYGB7cn0NCiMgQ2FyZ2Ftb3MgbG9zIGRhdG9zIChjb24gY29uanVudG9zIGluY2x1aWRvcyBlbiBsYXMgbGlicmVyw61hcykNCiNkYXRhKHBhY2thZ2U9J3RtYXAnKQ0KZGF0YShsYW5kLCByaXZlcnMsIG1ldHJvKSAgIyBsYW5kIGNvdmVyLCByaXZlcnMsIG1ldHJvcG9saXRhbiBhcmVhcw0KIyBsYW5kIGVzIGRlIHRpcG8gcmFzdGVyIChzdGFycy1yYXN0ZXIpLCBsYXMgb3RyYXMgZG9zIHNvbiBkZSB0aXBvIHNmDQoNCiMgVHJhbnNmb3JtYW1vcyBTRiBlbiBTUA0Kcml2ZXJzPC1hcyhyaXZlcnMsJ1NwYXRpYWwnKQ0KbWV0cm88LWFzKG1ldHJvLCAnU3BhdGlhbCcpDQoNCiMgU2Fsdm8gcGFyYSB1c2FyIGVzdG9zIGPDs2RpZ29zIG5vIGVzIG5lY2VzYXJpbyBwYXNhciBhIHRpcG8gc3ANCnN0cihsYW5kLG1heC5sZXZlbCA9IDIpDQojI3N0cihsYW5kQGRhdGEpDQpzdHIocml2ZXJzLG1heC5sZXZlbCA9IDIpDQpzdHIocml2ZXJzQGRhdGEpDQpzdHIobWV0cm8sbWF4LmxldmVsID0gMikNCnN0cihtZXRyb0BkYXRhKQ0KYGBgDQoNCg0KUmVwcmVzZW50YW1vcyB0b2RvcyBzaW11bHTDoW5lYW1lbnRlIChsdWVnbyBleHBsaWNhcmVtb3MgcGFzbyBhIHBhc28pDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCdwbG90JykNCnRtX3NoYXBlKGxhbmQpICsgDQogICAgdG1fcmFzdGVyKCJ0cmVlcyIsIGJyZWFrcz1zZXEoMCwgMTAwLCBieT0yMCksIGxlZ2VuZC5zaG93ID0gRkFMU0UpICsgDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCiAgICB0bV9ib3JkZXJzKCkgKw0KdG1fc2hhcGUocml2ZXJzKSArDQogICAgdG1fbGluZXMobHdkPSJzdHJva2Vsd2QiLCBzY2FsZT01LCBsZWdlbmQubHdkLnNob3cgPSBGQUxTRSkgKw0KdG1fc2hhcGUobWV0cm8pICsNCiAgICB0bV9idWJibGVzKCJwb3AyMDEwIiwgInJlZCIsIGJvcmRlci5jb2wgPSAiYmxhY2siLCBib3JkZXIubHdkPTEsIA0KICAgICAgICBzaXplLmxpbSA9IGMoMCwgMTFlNiksIHNpemVzLmxlZ2VuZCA9IGMoMWU2LCAyZTYsIDRlNiwgNmU2LCAxMGU2KSwgDQogICAgICAgIHRpdGxlLnNpemU9Ik1ldHJvcG9saXRhbiBQb3B1bGF0aW9uIikgKw0KICAgIHRtX3RleHQoIm5hbWUiLCBzaXplPSJwb3AyMDEwIiwgc2NhbGU9MSwgcm9vdD00LCBzaXplLmxvd2VyYm91bmQgPSAuNiwgDQogICAgICAgIGJnLmNvbG9yPSJ3aGl0ZSIsIGJnLmFscGhhID0gLjc1LCANCiAgICAgICAgYXV0by5wbGFjZW1lbnQgPSAxLCBsZWdlbmQuc2l6ZS5zaG93ID0gRkFMU0UpICsgDQp0bV9mb3JtYXQoIldvcmxkIikgKw0KdG1fc3R5bGUoIm5hdHVyYWwiKQ0KDQpgYGANClBhc28gYSBwYXNvDQoNCmBgYHtyfQ0KIyBjYXBhIGRlIGxhbmQgY292ZXIgY29uIHVuYSBjYXBhIHJhc3RlciBkZSBwb3JjZW50YWplIGRlIHN1cGVyZmljaWUgY3ViaWVydGEgcG9yIMOhcmJvbGVzIChncmFkaWVudGUgZGUgY29sb3IgZGFkbyBwb3IgYnJlYWtzKQ0KIHA8LXRtX3NoYXBlKGxhbmQpICsgDQogICAgdG1fcmFzdGVyKCJ0cmVlcyIsIGJyZWFrcz1zZXEoMCwgMTAwLCBieT0yMCksIGxlZ2VuZC5zaG93ID0gRkFMU0UpDQogIHNob3cocCkNCmBgYA0KDQpQYXNvIDENCmBgYHtyfQ0KIyBBw7FhZGlyIGNhcGEgZGUgZGF0b3MgZGUgRXVyb3BhIHkgZGVmaW5pcmxvIGNvbW8gY2FwYSBtYWVzdHJhLCBtb3N0cmFyIHN1cyBmcm9udGVyYXMNCg0KcDwtdG1fc2hhcGUobGFuZCkgKyANCnRtX3Jhc3RlcigidHJlZXMiLCBicmVha3M9c2VxKDAsIDEwMCwgYnk9MjApLCBsZWdlbmQuc2hvdyA9IEZBTFNFKSArDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCnRtX2JvcmRlcnMoKSAgIA0Kc2hvdyhwKQ0KYGBgDQoNClBhc28gMg0KDQpgYGB7cn0NCiMgQcOxYWRpciBjYXBhIGRlIHLDrW9zLCBlc2NhbGFkb3MgcG9yIHN1IGNhdWRhbA0KdG1fc2hhcGUobGFuZCkgKyANCnRtX3Jhc3RlcigidHJlZXMiLCBicmVha3M9c2VxKDAsIDEwMCwgYnk9MjApLCBsZWdlbmQuc2hvdyA9IEZBTFNFKSArDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCnRtX2JvcmRlcnMoKSArDQp0bV9zaGFwZShyaXZlcnMpKw0KdG1fbGluZXMobHdkPSJzdHJva2Vsd2QiLCBzY2FsZT01LCBsZWdlbmQubHdkLnNob3cgPSBGQUxTRSkNCg0KYGBgDQoNClBhc28gMw0KDQpgYGB7cn0NCiMgQcOxYWRpciBjYXBhIGRlIGJ1cmJ1amFzLCBkZSB0YW1hw7FvIHByb3BvcmNpb25hbCBhIGxhIHBvYmxhY2nDs24gbWV0cm9wb2xpdGFuYQ0KdG1fc2hhcGUobGFuZCkgKyANCnRtX3Jhc3RlcigidHJlZXMiLCBicmVha3M9c2VxKDAsIDEwMCwgYnk9MjApLCBsZWdlbmQuc2hvdyA9IEZBTFNFKSArDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCnRtX2JvcmRlcnMoKSArDQp0bV9zaGFwZShyaXZlcnMpKw0KdG1fbGluZXMobHdkPSJzdHJva2Vsd2QiLCBzY2FsZT01LCBsZWdlbmQubHdkLnNob3cgPSBGQUxTRSkrDQp0bV9zaGFwZShtZXRybykrDQp0bV9idWJibGVzKCJwb3AyMDEwIiwgInJlZCIsIGJvcmRlci5jb2wgPSAiYmxhY2siLCBib3JkZXIubHdkPTEsIA0KICAgICAgICBzaXplLmxpbSA9IGMoMCwgMTFlNiksIHNpemVzLmxlZ2VuZCA9IGMoMWU2LCAyZTYsIDRlNiwgNmU2LCAxMGU2KSwgDQogICAgICAgIHRpdGxlLnNpemU9Ik1ldHJvcG9saXRhbiBQb3B1bGF0aW9uIikgDQogICAgICAgIA0KYGBgDQoNClBhc28gNA0KDQpgYGB7cn0NCiMgQcOxYWRpciBjYXBhIGRlIHRleHRvIGRlIG5vbWJyZXMgZGUgbWV0csOzcG9saXMNCnRtX3NoYXBlKGxhbmQpICsgDQp0bV9yYXN0ZXIoInRyZWVzIiwgYnJlYWtzPXNlcSgwLCAxMDAsIGJ5PTIwKSwgbGVnZW5kLnNob3cgPSBGQUxTRSkgKw0KdG1fc2hhcGUoRXVyb3BlLCBpcy5tYXN0ZXIgPSBUUlVFKSArDQp0bV9ib3JkZXJzKCkgKw0KdG1fc2hhcGUocml2ZXJzKSsNCnRtX2xpbmVzKGx3ZD0ic3Ryb2tlbHdkIiwgc2NhbGU9NSwgbGVnZW5kLmx3ZC5zaG93ID0gRkFMU0UpKw0KdG1fc2hhcGUobWV0cm8pKw0KdG1fYnViYmxlcygicG9wMjAxMCIsICJyZWQiLCBib3JkZXIuY29sID0gImJsYWNrIiwgYm9yZGVyLmx3ZD0xLCANCiAgICAgICAgc2l6ZS5saW0gPSBjKDAsIDExZTYpLCBzaXplcy5sZWdlbmQgPSBjKDFlNiwgMmU2LCA0ZTYsIDZlNiwgMTBlNiksIA0KICAgICAgICB0aXRsZS5zaXplPSJNZXRyb3BvbGl0YW4gUG9wdWxhdGlvbiIpKyANCiB0bV90ZXh0KCJuYW1lIiwgc2l6ZT0icG9wMjAxMCIsIHNjYWxlPTEsIHJvb3Q9NCwgc2l6ZS5sb3dlcmJvdW5kID0gLjYsIA0KICAgICAgICBiZy5jb2xvcj0id2hpdGUiLCBiZy5hbHBoYSA9IC43NSwgDQogICAgICAgIGF1dG8ucGxhY2VtZW50ID0gMSwgbGVnZW5kLnNpemUuc2hvdyA9IEZBTFNFKQ0KICAgICAgICAgDQogDQpgYGANCg0KUGFzbyA1DQoNCmBgYHtyfQ0KIyBBw7FhZGlyIGZvcm1hdG86IGF2YWlsYWJsZSBmb3JtYXRzIGFyZTogIldvcmxkIiwgIldvcmxkX3dpZGUiLCAiTkxEIiwgIk5MRF93aWRlIiANCnRtX3NoYXBlKGxhbmQpICsgDQp0bV9yYXN0ZXIoInRyZWVzIiwgYnJlYWtzPXNlcSgwLCAxMDAsIGJ5PTIwKSwgbGVnZW5kLnNob3cgPSBGQUxTRSkgKw0KdG1fc2hhcGUoRXVyb3BlLCBpcy5tYXN0ZXIgPSBUUlVFKSArDQp0bV9ib3JkZXJzKCkgKw0KdG1fc2hhcGUocml2ZXJzKSsNCnRtX2xpbmVzKGx3ZD0ic3Ryb2tlbHdkIiwgc2NhbGU9NSwgbGVnZW5kLmx3ZC5zaG93ID0gRkFMU0UpKw0KdG1fc2hhcGUobWV0cm8pKw0KdG1fYnViYmxlcygicG9wMjAxMCIsICJyZWQiLCBib3JkZXIuY29sID0gImJsYWNrIiwgYm9yZGVyLmx3ZD0xLCANCiAgICAgICAgc2l6ZS5saW0gPSBjKDAsIDExZTYpLCBzaXplcy5sZWdlbmQgPSBjKDFlNiwgMmU2LCA0ZTYsIDZlNiwgMTBlNiksIA0KICAgICAgICB0aXRsZS5zaXplPSJNZXRyb3BvbGl0YW4gUG9wdWxhdGlvbiIpKyANCiB0bV90ZXh0KCJuYW1lIiwgc2l6ZT0icG9wMjAxMCIsIHNjYWxlPTEsIHJvb3Q9NCwgc2l6ZS5sb3dlcmJvdW5kID0gLjYsIA0KICAgICAgICBiZy5jb2xvcj0id2hpdGUiLCBiZy5hbHBoYSA9IC43NSwgDQogICAgICAgIGF1dG8ucGxhY2VtZW50ID0gMSwgbGVnZW5kLnNpemUuc2hvdyA9IEZBTFNFKSsgDQp0bV9mb3JtYXQoIldvcmxkIikgDQpgYGANCg0KUGFzbyA2DQoNCmBgYHtyfQ0KIyBlc3RpbG8gZGUgcmVwcmVzZW50YWNpw7NuIChnYW1hIGRlIGNvbG9yZXMpDQp0bV9zaGFwZShsYW5kKSArIA0KdG1fcmFzdGVyKCJ0cmVlcyIsIGJyZWFrcz1zZXEoMCwgMTAwLCBieT0yMCksIGxlZ2VuZC5zaG93ID0gRkFMU0UpICsNCnRtX3NoYXBlKEV1cm9wZSwgaXMubWFzdGVyID0gVFJVRSkgKw0KdG1fYm9yZGVycygpICsNCnRtX3NoYXBlKHJpdmVycykrDQp0bV9saW5lcyhsd2Q9InN0cm9rZWx3ZCIsIHNjYWxlPTUsIGxlZ2VuZC5sd2Quc2hvdyA9IEZBTFNFKSsNCnRtX3NoYXBlKG1ldHJvKSsNCnRtX2J1YmJsZXMoInBvcDIwMTAiLCAicmVkIiwgYm9yZGVyLmNvbCA9ICJibGFjayIsIGJvcmRlci5sd2Q9MSwgDQogICAgICAgIHNpemUubGltID0gYygwLCAxMWU2KSwgc2l6ZXMubGVnZW5kID0gYygxZTYsIDJlNiwgNGU2LCA2ZTYsIDEwZTYpLCANCiAgICAgICAgdGl0bGUuc2l6ZT0iTWV0cm9wb2xpdGFuIFBvcHVsYXRpb24iKSsgDQogdG1fdGV4dCgibmFtZSIsIHNpemU9InBvcDIwMTAiLCBzY2FsZT0xLCByb290PTQsIHNpemUubG93ZXJib3VuZCA9IC42LCANCiAgICAgICAgYmcuY29sb3I9IndoaXRlIiwgYmcuYWxwaGEgPSAuNzUsIA0KICAgICAgICBhdXRvLnBsYWNlbWVudCA9IDEsIGxlZ2VuZC5zaXplLnNob3cgPSBGQUxTRSkrIA0KdG1fZm9ybWF0KCJXb3JsZCIpICsNCnRtX3N0eWxlKCJuYXR1cmFsIikNCiANCmBgYA0KIyMjIyBDb25zaWRlcmFjaW9uZXMgc29icmUgZWwgbWFwYSBhbnRlcmlvcg0KDQorIEVzdGUgbWFwYSB0aWVuZSA0IGdydXBvcyBkZSBjYXBhcywgcmVzcGVjdGl2YW1lbnRlIGxvcyBvYmpldG9zICoqbGFuZCwgRXVyb3BlLCByaXZlcnMsIHkgbWV0cm8qKi4gRWwgb3JkZW4gZGUgZ3J1cG8gKGNhcGEpIGNvcnJlc3BvbmRlIGFsIG9yZGVuIGVuIHF1ZSBzZSBkaWJ1amEuDQorIExvcyBvYmpldG9zIHB1ZWRlbiB0ZW5lciAqKmRpZmVyZW50ZXMgcHJveWVjY2lvbmVzKiogeSB0YW1iacOpbiBwdWVkZW4gY3VicmlyIGRpZmVyZW50ZXMgw6FyZWFzICgqKmJvdW5kaW5nIGJveGVzKiopLiBUYW50byBsYSBgcHJveWVjY2nDs25gIGNvbW8gYGVsIMOhcmVhIGN1YmllcnRhYCBzZSB0b21hbiBwb3IgZGVmZWN0byBkZWwgb2JqZXRvIGRlZmluaWRvIGVuIGxhICoqcHJpbWVyYSB0bV9zaGFwZSoqLCBwZXJvIGVuIGVzdGUgY2FzbyBlbiBsYSBzZWd1bmRhICoqdG1fc2hhcGUqKiB5YSBxdWUgZXMgZW4gbGEgcXVlIHNlIGhhIHVzYWRvICoqaXMubWFzdGVyPVRSVUUqKi4gVGVuIGVuIGN1ZW50YSBxdWUgdG9kb3MgbG9zIG9iamV0b3MgdGllbmVuIGVsZW1lbnRvcyBmdWVyYSBkZSBFdXJvcGEgKHZlciBwb3IgZWplbXBsbyAqKnF0bShyaXZlcnMpKiopLiANCg0KU2UgcHVlZGUgYWRlbcOhcyBhw7FhZGlyIHVuYSBjYXBhICoqdG1fbGF5b3V0KCkqKiBxdWUgY29udHJvbGEgYXNwZWN0b3MgY29tbyB0w610dWxvLCBtw6FyZ2VuZXMsIHJlbGFjacOzbiBkZSBhc3BlY3RvLCBldGMuDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCdwbG90JykNCnRtX3NoYXBlKHJpdmVycykrdG1fbGluZXMoKSt0bV9sYXlvdXQobWFpbi50aXRsZSA9IlLDrW9zIixtYWluLnRpdGxlLnBvc2l0aW9uID0gImNlbnRlciIpDQojIEdyb3NvciBkZSBsYSBsw61uZWEgZGVwZW5kaWVudGUgZGUgdW5hIHZhcmlhYmxlLg0KdG1fc2hhcGUocml2ZXJzKSt0bV9saW5lcyhsd2Q9InN0cm9rZWx3ZCIsIHNjYWxlPTUsIGxlZ2VuZC5sd2Quc2hvdyA9IEZBTFNFKSt0bV9sYXlvdXQobWFpbi50aXRsZSA9IlLDrW9zIixtYWluLnRpdGxlLnBvc2l0aW9uID0gImNlbnRlciIpDQp0bV9zaGFwZShtZXRybykrdG1fZG90cygpK3RtX2xheW91dChtYWluLnRpdGxlID0iTWV0cm8iLG1haW4udGl0bGUucG9zaXRpb24gPSAiY2VudGVyIikNCmBgYA0KDQojIyMgRWplbXBsb3M6DQoNClJlcHJlc2VudGFjacOzbiBkZSBkb3MgbWFwYXMgc2ltdWx0w6FuZW1lbnRlIGNvbiBhc2lnbmFjacOzbiBkZSBDUlMNCg0KYGBge3J9DQojIFByb3llY2Npw7NuIFJvYmluc29uDQp0bWFwX21vZGUoJ3Bsb3QnKQ0Kcm9iaW4gPC0gIitwcm9qPXJvYmluICtsb25fMD0wICt4XzA9MCAreV8wPTAgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArdW5pdHM9bSArbm9fZGVmcyINCg0KbTEgPC0gdG1fc2hhcGUoV29ybGQsIHByb2plY3Rpb24gPSByb2JpbikgKw0KICB0bV9wb2x5Z29ucyhjKCJIUEkiLCAiZ2RwX2NhcF9lc3QiKSwNCiAgICAgICAgICAgICAgcGFsZXR0ZSA9IGxpc3QoIlJkWWxHbiIsICJQdXJwbGVzIiksDQogICAgICAgICAgICAgIHN0eWxlID0gYygicHJldHR5IiwgImZpeGVkIiksIG4gPSA3LCANCiAgICAgICAgICAgICAgYnJlYWtzID0gbGlzdChOVUxMLCBjKDAsIDUwMCwgMjAwMCwgNTAwMCwgMTAwMDAsIDI1MDAwLCA1MDAwMCwgSW5mKSksDQogICAgICAgICAgICAgIHRpdGxlID0gYygiSGFwcHkgUGxhbmV0IEluZGV4IiwgIkdEUCBwZXIgY2FwaXRhIikpICsNCiAgdG1fc3R5bGUoIm5hdHVyYWwiLCBlYXJ0aC5ib3VuZGFyeSA9IGMoLTE4MCwgLTg3LCAxODAsIDg3KSkgICsNCiAgdG1fZm9ybWF0KCJXb3JsZCIsIGlubmVyLm1hcmdpbnMgPSAwLjAyLCBmcmFtZSA9IEZBTFNFKSArDQogIHRtX2xlZ2VuZChwb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIiksIGJnLmNvbG9yID0gImdyYXk5NSIsIGZyYW1lID0gVFJVRSkgKw0KICB0bV9jcmVkaXRzKGMoIiIsICJSb2JpbnNvbiBwcm9qZWN0aW9uIiksIHBvc2l0aW9uID0gYygiUklHSFQiLCAiQk9UVE9NIikpDQptMQ0KYGBgDQoNClZpc3VhbGl6YWNpw7NuIGRlIHRyZXMgbWFnbml0dWRlcyBlbiBlbCBtaXNtbyBtYXBhDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCdwbG90JykNCm1ldHJvJGdyb3d0aCA8LSAobWV0cm8kcG9wMjAyMCAtIG1ldHJvJHBvcDIwMTApIC8gKG1ldHJvJHBvcDIwMTAgKiAxMCkgKiAxMDANCg0KbTIgPC0gdG1fc2hhcGUoV29ybGQpICsNCiAgICB0bV9wb2x5Z29ucygiaW5jb21lX2dycCIsIHBhbGV0dGUgPSAiLUJsdWVzIiwgDQogICAgICB0aXRsZSA9ICJJbmNvbWUgY2xhc3MiLCBjb250cmFzdCA9IDAuNywgYm9yZGVyLmNvbCA9ICJncmV5MzAiLCBpZCA9ICJuYW1lIikgKw0KICAgIHRtX3RleHQoImlzb19hMyIsIHNpemUgPSAiQVJFQSIsIGNvbCA9ICJncmV5MzAiLCByb290ID0gMykgKw0KICB0bV9zaGFwZShtZXRybykgKw0KICAgIHRtX2J1YmJsZXMoInBvcDIwMTAiLCBjb2wgPSAiZ3Jvd3RoIiwgYm9yZGVyLmNvbCA9ICJibGFjayIsDQogICAgICBib3JkZXIuYWxwaGEgPSAwLjUsDQogICAgICBicmVha3MgPSBjKC1JbmYsIDAsIDIsIDQsIDYsIEluZikgLA0KICAgICAgcGFsZXR0ZSA9ICItUmRZbEduIiwNCiAgICAgIHRpdGxlLnNpemUgPSAiTWV0cm8gcG9wdWxhdGlvbiAoMjAxMCkiLCANCiAgICAgIHRpdGxlLmNvbCA9ICJBbm51YWwgZ3Jvd3RoIHJhdGUgKCUpIiwNCiAgICAgIGlkID0gIm5hbWUiLA0KICAgICAgcG9wdXAudmFycyA9IGMoInBvcDIwMTAiLCAicG9wMjAyMCIsICJncm93dGgiKSkgKyANCiAgdG1fc3R5bGUoImdyYXkiKSArDQogIHRtX2Zvcm1hdCgiV29ybGQiLCBmcmFtZS5sd2QgPSAyKQ0KbTINCmBgYA0KDQpGaWphciB1biAibWFya2VyIiBtZWRpYW50ZSBjb29yZGVuYWRhcyBnZW9ncsOhZmljYXMgYSBwYXJ0aXIgZGUgZGlyZWNjacOzbi4NCg0KYGBge3J9DQojIG9idGFpbiBnZW9jb2RlIGFkZHJlc3MgaW5mb3JtYXRpb24NCmV0c2UgPC0gZ2VvY29kZV9PU00oJ0VUU0UsIEJ1cmphc3NvdCwgU3BhaW4nLCAgYXMuc2YgPSBUUlVFKQ0KDQojIGNoYW5nZSB0byBpbnRlcmFjdGl2ZSBtb2RlDQp0bWFwX21vZGUoInZpZXciKQ0KICB0bV9zaGFwZShldHNlKSArDQoJdG1fbWFya2Vycyh0ZXh0PSJxdWVyeSIpDQpgYGANCg0KIyMjIEd1YXJkYXIgdW4gbWFwYTogKip0bWFwX3NhdmUoKSoqDQoNClBlcm1pdGUgZ3JhYmFyIG1hcGFzIGVzdMOhdGljb3MgeSB0YW1iaWVuIElOVEVSQUNUSVZPUy4NCg0KYGBge3J9DQpsaWJyYXJ5KHNwKQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShnZW9zcGF0aWFsKQ0KDQp0bWFwX21vZGUoJ3Bsb3QnKQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpICsNCiAgdG1fZ3JpZChuLnggPSAxMSwgbi55ID0gMTEsIHByb2plY3Rpb24gPSAiK3Byb2o9bG9uZ2xhdCIpICsNCiAgdG1fZmlsbChjb2wgPSAicG9wdWxhdGlvbiIsIHN0eWxlID0gInF1YW50aWxlIixhbHBoYSA9IDAuMikgICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYnVybHl3b29kNCIpDQoNCiMgR3VhcmRhciB1biBtYXBhIEVTVMOBVElDTw0KdG1hcF9zYXZlKGZpbGVuYW1lPSJwb3B1bGF0aW9uLnBuZyIpDQoNCiMgU2F2ZSB1biBtYXBhIElOVEVSQUNUSVZPDQp0bWFwX21vZGUoJ3ZpZXcnKQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpICsNCiAgdG1fZ3JpZChuLnggPSAxMSwgbi55ID0gMTEsIHByb2plY3Rpb24gPSAiK3Byb2o9bG9uZ2xhdCIpICsNCiAgdG1fZmlsbChjb2wgPSAicG9wdWxhdGlvbiIsIHN0eWxlID0gInF1YW50aWxlIixhbHBoYSA9IDAuMikgICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYnVybHl3b29kNCIpDQp0bWFwX3NhdmUoZmlsZW5hbWU9InBvcHVsYXRpb24uaHRtbCIpDQoNCiMgTGEgb3BjacOzbiBwb3IgZGVmZWN0bywgY3VhbmRvIHNlIHV0aWxpemEgdG1hcCBlcyBxdWUgZWwgbWFwYSBzZWEgaW50ZXJhY3Rpdm8uDQogIA0KYGBgDQoNCiMjIyBJbnRlZ3JhY2nDs24gZW4gc2hpbnkNCg0KUG9kZW1vcyBpbmNydXN0YXIgbWFwYXMgZGUgdG1hcCBlbiBzaGlueSBjb24gbGEgZnVuY2nDs24gKip0bWFwT3V0cHV0KCkqKiBlbiBsYSBwYXJ0ZSBkZSBVSSB5ICoqcmVuZGVyVG1hcCgpKiogZW4gZWwgc2VydmVyOg0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnVpIDwtIGZsdWlkUGFnZSgNCiAgdG1hcE91dHB1dCgibXlfdG1hcCIpDQopDQoNCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCxvdXRwdXQpIHsNCiAgb3V0cHV0JG15X3RtYXAgPSByZW5kZXJUbWFwKHsNCiAgICB0bV9zaGFwZShXb3JsZCwgcHJvamVjdGlvbj0iK3Byb2o9cm9iaW4iKSArIHRtX3BvbHlnb25zKCJIUEkiLCBsZWdlbmQudGl0bGUgPSAiSGFwcHkgUGxhbmV0IEluZGV4IikgKyB0bV9zdHlsZSgnY29iYWx0JykNCiAgfSkNCn0NCg0Kc2hpbnlBcHAodWksIHNlcnZlcikNCg0KYGBgDQoNCg0KDQojIyBFamVyY2ljaW8NCg0KDQoxLiBEaWJ1amEsIGVuIG1vZG8gcGxvdCwgZWwgbWFwYSBkZWwgbXVuZG8gKGNvdW50cmllc19zcGRmKSwgZGUgbGEgbGlicmVyw61hICoqZ2Vvc3BhdGlhbCoqICh1c2EgdG1fWFhYKS4NCg0KYGBge3J9DQp0bWFwX21vZGUoJ3Bsb3QnKQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpK3RtX3BvbHlnb25zKCkNCmBgYA0KDQoyLiBEaWJ1amEgZWwgbWFwYSBkZWwgbXVuZG8gKGNvdW50cmllc19zcGRmKSB5IGNvbG9yZWEgbG9zIHBhaXNlcyBzZWfDum4gZWwgY29udGluZW50ZS4NCg0KYGBge3J9DQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikrdG1fZmlsbChjb2w9J3JlZ2lvbicpDQpgYGANCg0KMy4gQcOxYWRlIGxhcyBpbmljaWFsZXMgZGUgbG9zIHBhaXNlcyAoKippc29fYTMqKikgY29uIHRtX3RleHQgKHVzYSBzaXplPTEpLg0KDQpgYGB7cn0NCnRtX3NoYXBlKGNvdW50cmllc19zcGRmKSt0bV9maWxsKGNvbD0ncmVnaW9uJykrdG1fdGV4dCgnaXNvX2EzJyxzaXplPTEpDQpgYGANCg0KNC4gQcOxYWRlIGxhcyBpbmljaWFsZXMgZGUgbG9zIHBhaXNlcyAoKippc29fYTMqKikgY29uIHVuIHRhbWHDsW8gcXVlIHNlYSBwcm9wb3JjaW9uYWwgYWwgw6FyZWEgZGVsIHBhw61zIChtaXJhIGF5dWRhIGRlICoqdG1fdGV4dCoqKS4gUmVwaXRlIHBlcm8gY29uIGVsIHRleHRvIHByb3BvcmNpb25hbCBhIGxhIHBvYmxhY2nDs24uDQoNCmBgYHtyfQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpK3RtX2ZpbGwoY29sPSdyZWdpb24nKSt0bV90ZXh0KCdpc29fYTMnLHNpemU9J0FSRUEnKQ0KDQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikrdG1fZmlsbChjb2w9J3JlZ2lvbicpK3RtX3RleHQoJ2lzb19hMycsc2l6ZT0ncG9wdWxhdGlvbicpDQoNCmBgYA0KDQo1LiBEaWJ1amEgbG9zIHBhaXNlcyB5IGNvbG9yw6lhbG9zIHNlZ8O6biBsYSBERU5TSURBRCBkZSBQT0JMQUNJw5NOIChoYWJpdGFudGVzL2ttMikuIExhIGZ1bmNpw7NuICphcmVhKCkqIGRlbCBwYXF1ZXRlICoqcmFzdGVyKiogcGVybWl0ZSBjYWxjdWxhciBlbCDDoXJlYSBkZSBjYWRhIHBvbMOtZ29ubyBkZSB1biBvYmpldG8gU3BhdGlhbFBvbHlnb25zRGF0YUZyYW1lLCBlbiBtZXRyb3MgY3VhZHJhZG9zLiBQcnVlYmEgZWwgZWZlY3RvIGRlIGxhIG9wY2nDs24gKipzdHlsZT0ncXVhbnRpbGUnKiogZW4gKip0bV9maWxsKCkqKiwgc29icmUgbGEgZGVuc2lkYWQuDQoNCmBgYHtyfQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShyYXN0ZXIpDQoNCiMgQ2FsY3VsYXIgZWwgw6FyZWEgZGUgY2FkYSBwYcOtcyBlbiBrbV4yDQpjb3VudHJpZXNfc3BkZiRhcmVhX2ttMiA8LSBhcmVhKGNvdW50cmllc19zcGRmKS8xMF42DQoNCiMgQ2FsY3VsYXIgbGEgZGVuc2lkYWQgZGUgcG9ibGFjacOzbg0KY291bnRyaWVzX3NwZGYkZGVuc2l0eSA8LSBjb3VudHJpZXNfc3BkZiRwb3B1bGF0aW9uL2NvdW50cmllc19zcGRmJGFyZWFfa20yDQoNCiMgQ3JlYXIgdW4gbWFwYSBjb24gbGEgZGVuc2lkYWQgZGUgcG9ibGFjacOzbiBjb21vIHZhcmlhYmxlIGRlIGNvbG9yDQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikgKw0KICB0bV9maWxsKGNvbCA9ICJkZW5zaXR5Iiwgc3R5bGUgPSAicXVhbnRpbGUiKSArDQogIHRtX3RleHQoImlzb19hMyIsIHNpemUgPSAiYXJlYV9rbTIiKSArDQogIHRtX2xheW91dChmcmFtZSA9IEZBTFNFKQ0KYGBgDQoNCjYuIFJlcGl0ZSBlbCBtYXBhIGFudGVyaW9yIHBlcm8gZGlidWphIHNvbG8gRXVyb3BhLCBzaW4gIlJ1c3NpYSIuDQoNCmBgYHtyfQ0KDQpFdXJvcGVfd29fcnVzc2lhIDwtIGNvdW50cmllc19zcGRmW2NvdW50cmllc19zcGRmJG5hbWUhPSdSdXNzaWEnICYgY291bnRyaWVzX3NwZGYkcmVnaW9uPT0nRXVyb3BlJyxdDQoNCiMgQ2FsY3VsYXIgZWwgw6FyZWEgZGUgY2FkYSBwYcOtcyBlbiBrbV4yDQpFdXJvcGVfd29fcnVzc2lhJGFyZWFfa20yIDwtIGFyZWEoRXVyb3BlX3dvX3J1c3NpYSkvMTBeNg0KDQojIENhbGN1bGFyIGxhIGRlbnNpZGFkIGRlIHBvYmxhY2nDs24NCkV1cm9wZV93b19ydXNzaWEkZGVuc2l0eSA8LSBFdXJvcGVfd29fcnVzc2lhJHBvcHVsYXRpb24vRXVyb3BlX3dvX3J1c3NpYSRhcmVhX2ttMg0KDQojIENyZWFyIHVuIG1hcGEgY29uIGxhIGRlbnNpZGFkIGRlIHBvYmxhY2nDs24gY29tbyB2YXJpYWJsZSBkZSBjb2xvcg0KdG1fc2hhcGUoRXVyb3BlX3dvX3J1c3NpYSkgKw0KICB0bV9maWxsKGNvbCA9ICJkZW5zaXR5IikgKw0KICB0bV90ZXh0KCJpc29fYTMiLCBzaXplID0gImFyZWFfa20yIikgKw0KICB0bV9sYXlvdXQoZnJhbWUgPSBGQUxTRSkNCmBgYA0KDQo3LiBPYnNlcnZhIGVsIGVmZWN0byBkZWwgcGFyw6FtZXRybyAic3R5bGUiIGVuICoqdG1fZmlsbCoqLiBQcnVlYmEgKipzdHlsZT0icXVhbnRpbGUiKioNCg0KYGBge3J9DQp0bV9zaGFwZShFdXJvcGVfd29fcnVzc2lhKSArDQogIHRtX2ZpbGwoY29sID0gImRlbnNpdHkiLCBzdHlsZSA9ICJxdWFudGlsZSIpICsNCiAgdG1fdGV4dCgiaXNvX2EzIiwgc2l6ZSA9ICJhcmVhX2ttMiIpICsNCiAgdG1fbGF5b3V0KGZyYW1lID0gRkFMU0UpDQpgYGANCg0KOC4gR3VhcmRhIGVsIG1hcGEgZW4gbW9kbyBlc3TDoXRpY28uDQoNCmBgYHtyfQ0KdG1hcF9zYXZlKGZpbGVuYW1lID0gJ0V1cm9wYV9zaW5fUnVzaWEucG5nJykNCmBgYA0KDQo=